package Threading2;

import java.util.Scanner;

// 编译器的优化：此时亮哥线程直接操作这个 flag，预期效果：在控制台输入一个整数之后，这个进程就结束了
// 实际效果：这个进程并没有结束，原因：内存可见性引起的，while(flag == 0) 这个操作会有大概亮哥CPU
// 指令，1. 将flag的值从内存中读取到寄存器中，2. 进行比较操作，但是这个load（读取flag值）的操作所
// 耗费的时间 >> 进行比较的时间，然后编译器发现比较的值都是相同的，此时编译器就会做出优化，不再进行
// load 操作，直接用寄存器中的 flag 原有的值和 0 进行比较，此时第二个线程即使再去改变 flag 这个变量的
// 值，读到的值依然还是0，所以t1这个线程就没有结束，此时的整个进程也就没有结束

// 如何解决：让编译器针对这个场景暂停优化： volatile 关键字即可

public class ThreadDemo9 {
    volatile public static int flag = 0; // volatile 关键字的作用：保证每次编译器都会去内存中读取 flag 的值
    public static void main1(String[] args) {
        Thread t1 = new Thread(() -> {
           while (flag == 0) {
               // 啥也不做
               try {
                   Thread.sleep(10); // 此时如果 + sleep 代码，编译器就不会进行自动优化了，
                   // 因为此时的load操作不再是一个负担了，而 sleep 才是最耗费时间的，所以此时编译器就
                   // 不会再进行优化了
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
        });

        Thread t2 = new Thread(() -> {
            Scanner scanner = new Scanner(System.in);
            System.out.println("请输入一个整数：");
            flag = scanner.nextInt();
        });

        t1.start();
        t2.start();
        // 也就是说当在控制台输入一个整数之后，t1 这个线程中的 while 循环就结束了，最终这个Java进程就结束了
    }

    public static void main4(String[] args) {
        // 指令重排序引起的线程不安全
        // Student student = new Student();
        // 比如上述代码大概可以分成三个部分：1. 申请内存空间，2. 调用构造方法，3. 将引用额对象赋值给 student
        // 如果是单线程的情况下，此时的指令重排序是可以的，不会有bug，但是如果是多线程，比如：t1 线程先执行了1，3，
        // 之后 t2 线程执行 2，由于t1还没有调用构造方法来构造这个对象，然后 t2 线程就直接调用 student.learn() 方法
        // 但是这个 student 这个对象还没有真正的创建出来，此时就会引起 “空引用”
    }

    public static void main2(String[] args) {
        // 线程不安全的原因：
        // 1. 抢占式执行，随机调度（线程中的代码执行到任意一行，都随时可能被切出去）
        // 2. 多个线程同时修改同一个变量，（load add save 是三个 CPU 指令）
        // 3. 内存可见性，指令重排序

        // 解决方法：1. synchronized 加锁 以代码块的方式加锁，使需要串行执行的代码串行执行
        // 2. volatile 关键字，保证内存可见性，禁止指令重排序
    }


    public static Object locker = new Object();
    // 控制线程的执行顺序的有效手段：wait，notify

    // 这两个关键字也是需要搭配 synchronized 来一起使用的，wait就是让一个线程进入到 wait 的状态，进入到阻塞状态
    // notify 就是唤醒上述使用wait的线程，进入到运行的状态，唤醒操作，还有一个 notifyAll ，就是可以有多个线程，调用了 notifyAll就会唤醒所有的线程
    // 例如在 t1 t2 t3 中都调用 Object.wait，此时在 main 线程中调用了 object.notify，此时就会随机唤醒一个
    // 线程，（另外两个线程的状态依然还是 WAITING）
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            System.out.println("wait 开始!");
            try {
                synchronized (locker) {
                    locker.wait();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("wait 结束!");
        });

        Thread t2 = new Thread(() -> {
            System.out.println("notify 开始！");
            synchronized (locker) {
                locker.notify();
            }
            System.out.println("notify 结束！");
        });

        t1.start();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t2.start();
    }


}
